/***********************************************************************
filename:   CEGUIFont.cpp
created:    21/2/2004
author:     Paul D Turner

purpose:    Implements hgeFont class
*************************************************************************/
#include "hgeFont.h"
#include "hgeExceptions.h"
#include "hgeTexture.h"
#include "hgeImage.h"
#include "hgeImageManager.h"
#include "hgeLogger.h"
#include "hgePropertyHelper.h"
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#ifdef _MSC_VER
#define snprintf _snprintf
#endif

//----------------------------------------------------------------------------//
// amount of bits in a uint
#define BITS_PER_UINT   (sizeof (UINT) * 8)
// must be a power of two
#define GLYPHS_PER_PAGE 256

//----------------------------------------------------------------------------//
// Pixels to put between glyphs
#define INTER_GLYPH_PAD_SPACE 2
// A multiplication coefficient to convert FT_Pos values into normal floats
#define FT_POS_COEF  (1.0/64.0)

//----------------------------------------------------------------------------//
// Font objects usage count
static int ft_usage_count = 0;
// A handle to the FreeType library
static FT_Library ft_lib;

//----------------------------------------------------------------------------//
#undef __FTERRORS_H__
#define FT_ERRORDEF( e, v, s ) s,
#define FT_ERROR_START_LIST static const char* ft_errors[] = {
#define FT_ERROR_END_LIST 0};
#include FT_ERRORS_H

//----------------------------------------------------------------------------//
hgeFont::hgeFont(const String& font_name, const float point_size,
	const bool anti_aliased, const String& font_filename) :
	d_name(font_name),
	d_filename(font_filename),
	d_ascender(0),
	d_descender(0),
	d_height(0),
	d_maxCodepoint(0),
	d_glyphPageLoaded(0),
	d_ptSize(point_size),
	d_antiAliased(anti_aliased),
	d_fontFace(0)
{
	if (!ft_usage_count++)
		FT_Init_FreeType(&ft_lib);

	updateFont();

	char tmp[50];
	snprintf(tmp, sizeof(tmp), "Succsessfully loaded %d glyphs",
		static_cast<int>(d_cp_map.size()));
	hgeLogger::getSingleton().logEvent(tmp, Informative);
}

//----------------------------------------------------------------------------//
hgeFont::~hgeFont()
{
	free();

	if (!--ft_usage_count)
		FT_Done_FreeType(ft_lib);

	delete[] d_glyphPageLoaded;
}

//----------------------------------------------------------------------------//
UINT hgeFont::getTextureSize(CodepointMap::const_iterator s,
	CodepointMap::const_iterator e) const
{
	UINT texsize = 32; // start with 32x32
	UINT max_texsize = System::getSingleton().getRenderer()->getMaxTextureSize();
	UINT glyph_count = 0;

	// Compute approximatively the optimal texture size for font
	while (texsize < max_texsize)
	{
		UINT x = INTER_GLYPH_PAD_SPACE, y = INTER_GLYPH_PAD_SPACE;
		UINT yb = INTER_GLYPH_PAD_SPACE;
		for (CodepointMap::const_iterator c = s; c != e; ++c)
		{
			// skip glyphs that are already rendered
			if (c->second.getImage())
				continue;

			// load glyph metrics (don't render)
			if (FT_Load_Char(d_fontFace, c->first,
				FT_LOAD_DEFAULT | FT_LOAD_FORCE_AUTOHINT))
				continue;

			UINT glyph_w = int (ceil(d_fontFace->glyph->metrics.width * FT_POS_COEF)) +
				INTER_GLYPH_PAD_SPACE;
			UINT glyph_h = int (ceil(d_fontFace->glyph->metrics.height * FT_POS_COEF)) +
				INTER_GLYPH_PAD_SPACE;

			x += glyph_w;
			if (x > texsize)
			{
				x = INTER_GLYPH_PAD_SPACE;
				y = yb;
			}
			UINT yy = y + glyph_h;
			if (yy > texsize)
				goto too_small;

			if (yy > yb)
				yb = yy;

			++glyph_count;
		}
		// Okay, the texture size is enough for holding our glyphs
		break;

too_small:
		texsize *= 2;
	}

	return glyph_count ? texsize : 0;
}

//----------------------------------------------------------------------------//
void hgeFont::rasterise(utf32 start_codepoint, utf32 end_codepoint) const
{
	CodepointMap::const_iterator s = d_cp_map.lower_bound(start_codepoint);
	if (s == d_cp_map.end())
		return;

	CodepointMap::const_iterator orig_s = s;
	CodepointMap::const_iterator e = d_cp_map.upper_bound(end_codepoint);
	while (true)
	{
		// Create a new Imageset for glyphs
		UINT texsize = getTextureSize(s, e);
		// If all glyphs were already rendered, do nothing
		if (!texsize)
			break;
		
		Texture& tex = System::getSingleton().getRenderer()->createTexture(Size(texsize, texsize));
		d_glyphImages.push_back(&tex);

		// Create a memory buffer where we will render our glyphs
		argb_t *mem_buffer = new argb_t [texsize * texsize];
		memset(mem_buffer, 0, texsize * texsize * sizeof(argb_t));

		// Go ahead, line by line, top-left to bottom-right
		UINT x = INTER_GLYPH_PAD_SPACE, y = INTER_GLYPH_PAD_SPACE;
		UINT yb = INTER_GLYPH_PAD_SPACE;

		// Set to true when we finish rendering all glyphs we were asked to
		bool finished = false;
		// Set to false when we reach d_cp_map.end() and we start going backward
		bool forward = true;
		char buf[128];

		/* To conserve texture space we will render more glyphs than asked,
		* but never less than asked. First we render all glyphs from s to e
		* and after that we render glyphs until we reach d_cp_map.end(),
		* and if there's still free texture space we will go backward
		* from s until we hit d_cp_map.begin().
		*/
		while (s != d_cp_map.end())
		{
			// Check if we finished rendering all the required glyphs
			finished |= (s == e);

			// Check if glyph already rendered
			if (!s->second.getImage())
			{
				// Render the glyph
				if (FT_Load_Char(d_fontFace, s->first, FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT |
					(d_antiAliased ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO)))
				{
					std::stringstream err;
					err << "Font::loadFreetypeGlyph - Failed to load glyph for codepoint: ";
					err << static_cast<unsigned int>(s->first);
					err << ".  Will use an empty image for this glyph!";
					hgeLogger::getSingleton().logEvent(err.str(), Errors);

					// Create a 'null' image for this glyph so we do not seg later
					hgeRect area(0, 0, 0, 0);
					Point offset(0, 0);
					
					sprintf(buf, "%s_%s%d", d_name.c_str(), "_auto_glyph_images_", int (s->first));
					String name(buf);

					Image& image = ImageManager::getSingleton().createImage(name, tex, area, offset);
					((hgeFontGlyph &)s->second).setImage(&image);
				}
				else
				{
					UINT glyph_w = d_fontFace->glyph->bitmap.width + INTER_GLYPH_PAD_SPACE;
					UINT glyph_h = d_fontFace->glyph->bitmap.rows + INTER_GLYPH_PAD_SPACE;

					// Check if glyph right margin does not exceed texture size
					UINT x_next = x + glyph_w;
					if (x_next > texsize)
					{
						x = INTER_GLYPH_PAD_SPACE;
						x_next = x + glyph_w;
						y = yb;
					}

					// Check if glyph bottom margine does not exceed texture size
					UINT y_bot = y + glyph_h;
					if (y_bot > texsize)
						break;

					// Copy rendered glyph to memory buffer in RGBA format
					drawGlyphToBuffer(mem_buffer + (y * texsize) + x, texsize);

					// Create a new image in the imageset
					hgeRect area(static_cast<float>(x),
						static_cast<float>(y),
						static_cast<float>(x + glyph_w - INTER_GLYPH_PAD_SPACE),
						static_cast<float>(y + glyph_h - INTER_GLYPH_PAD_SPACE));

					Point offset(d_fontFace->glyph->metrics.horiBearingX * static_cast<float>(FT_POS_COEF),
						-d_fontFace->glyph->metrics.horiBearingY * static_cast<float>(FT_POS_COEF));

					sprintf(buf, "%s_%s%d", d_name.c_str(), "_auto_glyph_images_", int (s->first));
					String name(buf);

					Image& image = ImageManager::getSingleton().createImage(name, tex, area, offset);
					((hgeFontGlyph &)s->second).setImage(&image);

					// Advance to next position
					x = x_next;
					if (y_bot > yb)
					{
						yb = y_bot;
					}
				}
			}

			// Go to next glyph, if we are going forward
			if (forward)
				if (++s == d_cp_map.end())
				{
					finished = true;
					forward = false;
					s = orig_s;
				}
				// Go to previous glyph, if we are going backward
				if (!forward)
					if ((s == d_cp_map.begin()) || (--s == d_cp_map.begin()))
						break;
		}

		// Copy our memory buffer into the texture and free it
		tex.loadFromMemory(mem_buffer, Size(texsize, texsize), Texture::PF_RGBA);
		delete [] mem_buffer;

		if (finished)
			break;
	}
}

//----------------------------------------------------------------------------//
void hgeFont::drawGlyphToBuffer(argb_t *buffer, UINT buf_width) const
{
	FT_Bitmap *glyph_bitmap = &d_fontFace->glyph->bitmap;

	for (int i = 0; i < glyph_bitmap->rows; ++i)
	{
		UCHAR *src = glyph_bitmap->buffer + (i * glyph_bitmap->pitch);
		switch (glyph_bitmap->pixel_mode)
		{
		case FT_PIXEL_MODE_GRAY:
			{
				UCHAR *dst = reinterpret_cast<UCHAR*>(buffer);
				for (int j = 0; j < glyph_bitmap->width; ++j)
				{
					// RGBA
					*dst++ = 0xFF;
					*dst++ = 0xFF;
					*dst++ = 0xFF;
					*dst++ = *src++;
				}
			}
			break;

		case FT_PIXEL_MODE_MONO:
			for (int j = 0; j < glyph_bitmap->width; ++j)
				buffer [j] = (src [j / 8] & (0x80 >> (j & 7))) ? 0xFFFFFFFF : 0x00000000;
			break;

		default:
			throw InvalidRequestException("Font::drawGlyphToBuffer: "
				"The glyph could not be drawn because the pixel mode is "
				"unsupported.");
			break;
		}

		buffer += buf_width;
	}
}

//----------------------------------------------------------------------------//
void hgeFont::free()
{
	if (!d_fontFace)
		return;

	d_cp_map.clear();

	for (size_t i = 0; i < d_glyphImages.size(); i++)
		System::getSingleton().getRenderer()->destroyTexture(*d_glyphImages[i]);
	d_glyphImages.clear();

	FT_Done_Face(d_fontFace);
	d_fontFace = 0;
}

//----------------------------------------------------------------------------//
void hgeFont::updateFont()
{
	free();

	FT_Error error;

	// create face using input font
	if ((error = FT_New_Face(ft_lib, d_filename.c_str(), 0,&d_fontFace)) != 0)
		throw GenericException("hgeFont::updateFont: Failed to create "
		"face from font file '" + d_filename + "' error was: " +
		((error < FT_Err_Max) ? ft_errors[error] : "unknown error"));

	// check that default Unicode character map is available
	if (!d_fontFace->charmap)
	{
		FT_Done_Face(d_fontFace);
		d_fontFace = 0;
		throw GenericException("hgeFont::updateFont: "
			"The font '" + d_name + "' does not have a Unicode charmap, and "
			"cannot be used.");
	}

	UINT horzdpi = System::getSingleton().getRenderer()->getDisplayDPI().d_x;
	UINT vertdpi = System::getSingleton().getRenderer()->getDisplayDPI().d_y;

	float hps = d_ptSize * 64;
	float vps = d_ptSize * 64;
	
	if (FT_Set_Char_Size(d_fontFace, FT_F26Dot6(hps), FT_F26Dot6(vps), horzdpi, vertdpi))
	{
		// For bitmap fonts we can render only at specific point sizes.
		// Try to find nearest point size and use it, if that is possible
		float ptSize_72 = (d_ptSize * 72.0f) / vertdpi;
		float best_delta = 99999;
		float best_size = 0;
		for (int i = 0; i < d_fontFace->num_fixed_sizes; i++)
		{
			float size = d_fontFace->available_sizes [i].size * float(FT_POS_COEF);
			float delta = fabs(size - ptSize_72);
			if (delta < best_delta)
			{
				best_delta = delta;
				best_size = size;
			}
		}

		if ((best_size <= 0) ||
			FT_Set_Char_Size(d_fontFace, 0, FT_F26Dot6(best_size * 64), 0, 0))
		{
			char size [20];
			snprintf(size, sizeof(size), "%g", d_ptSize);
			throw GenericException("hgeFont::load - The font '" + d_name + "' cannot be rasterised at a size of " + size + " points, and cannot be used.");
		}
	}

	if (d_fontFace->face_flags & FT_FACE_FLAG_SCALABLE)
	{
		//float x_scale = d_fontFace->size->metrics.x_scale * FT_POS_COEF * (1.0/65536.0);
		float y_scale = d_fontFace->size->metrics.y_scale * float(FT_POS_COEF) * (1.0f / 65536.0f);
		d_ascender = d_fontFace->ascender * y_scale;
		d_descender = d_fontFace->descender * y_scale;
		d_height = d_fontFace->height * y_scale;
	}
	else
	{
		d_ascender = d_fontFace->size->metrics.ascender * float(FT_POS_COEF);
		d_descender = d_fontFace->size->metrics.descender * float(FT_POS_COEF);
		d_height = d_fontFace->size->metrics.height * float(FT_POS_COEF);
	}

	// Create an empty hgeFontGlyph structure for every glyph of the font
	FT_UInt gindex;
	FT_ULong codepoint = FT_Get_First_Char(d_fontFace, &gindex);
	FT_ULong max_codepoint = codepoint;
	while (gindex)
	{
		if (max_codepoint < codepoint)
			max_codepoint = codepoint;

		// load-up required glyph metrics (don't render)
		if (FT_Load_Char(d_fontFace, codepoint,
			FT_LOAD_DEFAULT | FT_LOAD_FORCE_AUTOHINT))
			continue; // glyph error

		float adv = d_fontFace->glyph->metrics.horiAdvance * float(FT_POS_COEF);

		// create a new empty hgeFontGlyph with given character code
		d_cp_map[codepoint] = hgeFontGlyph(adv);

		// proceed to next glyph
		codepoint = FT_Get_Next_Char(d_fontFace, codepoint, &gindex);
	}

	setMaxCodepoint(max_codepoint);
}

//----------------------------------------------------------------------------//
float hgeFont::getPointSize() const
{
	return d_ptSize;
}

//----------------------------------------------------------------------------//
bool hgeFont::isAntiAliased() const
{
	return d_antiAliased;
}

//----------------------------------------------------------------------------//
void hgeFont::setPointSize(const float point_size)
{
	if (point_size == d_ptSize)
		return;

	d_ptSize = point_size;
	updateFont();
}

//----------------------------------------------------------------------------//
void hgeFont::setAntiAliased(const bool anti_alaised)
{
	if (anti_alaised == d_antiAliased)
		return;

	d_antiAliased = anti_alaised;
	updateFont();
}

//----------------------------------------------------------------------------//
const String& hgeFont::getName() const
{
	return d_name;
}

//----------------------------------------------------------------------------//
const String& hgeFont::getTypeName() const
{
	return d_type;
}

//----------------------------------------------------------------------------//
void hgeFont::setMaxCodepoint(utf32 codepoint)
{
	d_maxCodepoint = codepoint;

	delete[] d_glyphPageLoaded;

	UINT npages = (codepoint + GLYPHS_PER_PAGE) / GLYPHS_PER_PAGE;
	UINT size = (npages + BITS_PER_UINT - 1) / BITS_PER_UINT;
	d_glyphPageLoaded = new UINT[size];
	memset(d_glyphPageLoaded, 0, size * sizeof(UINT));
}

//----------------------------------------------------------------------------//
const hgeFontGlyph* hgeFont::getGlyphData(utf32 codepoint) const
{
	if (codepoint > d_maxCodepoint)
		return 0;

	if (d_glyphPageLoaded)
	{
		// Check if glyph page has been rasterised
		UINT page = codepoint / GLYPHS_PER_PAGE;
		UINT mask = 1 << (page & (BITS_PER_UINT - 1));
		if (!(d_glyphPageLoaded[page / BITS_PER_UINT] & mask))
		{
			d_glyphPageLoaded[page / BITS_PER_UINT] |= mask;
			rasterise(codepoint & ~(GLYPHS_PER_PAGE - 1),
				codepoint | (GLYPHS_PER_PAGE - 1));
		}
	}

	CodepointMap::const_iterator pos = d_cp_map.find(codepoint);
	return (pos != d_cp_map.end()) ? &pos->second : 0;
}

//----------------------------------------------------------------------------//
float hgeFont::getTextExtent(const String& text, float x_scale) const
{
	const hgeFontGlyph* glyph;
	float cur_extent = 0, adv_extent = 0, width;

	for (size_t c = 0; c < text.length(); ++c)
	{
		glyph = getGlyphData(text[c]);

		if (glyph)
		{
			width = glyph->getRenderedAdvance(x_scale);

			if (adv_extent + width > cur_extent)
				cur_extent = adv_extent + width;

			adv_extent += glyph->getAdvance(x_scale);
		}
	}

	return max(adv_extent, cur_extent);
}

//----------------------------------------------------------------------------//
size_t hgeFont::getCharAtPixel(const String& text, size_t start_char, float pixel,
							float x_scale) const
{
	const hgeFontGlyph* glyph;
	float cur_extent = 0;
	size_t char_count = text.length();

	// handle simple cases
	if ((pixel <= 0) || (char_count <= start_char))
		return start_char;

	for (size_t c = start_char; c < char_count; ++c)
	{
		glyph = getGlyphData(text[c]);

		if (glyph)
		{
			cur_extent += glyph->getAdvance(x_scale);

			if (pixel < cur_extent)
				return c;
		}
	}

	return char_count;
}

//----------------------------------------------------------------------------//
void hgeFont::drawText(GeometryBuffer& buffer, const String& text,
					const Vector2& position, const hgeRect* clip_rect,
					const ColourRect& colours, const float space_extra,
					const float x_scale, const float y_scale)
{
	const float base_y = position.d_y + getBaseline(y_scale);
	Vector2 glyph_pos(position);

	for (size_t c = 0; c < text.length(); ++c)
	{
		const hgeFontGlyph* glyph;
		if ((glyph = getGlyphData(text[c]))) // NB: assignment
		{
			const Image* const img = glyph->getImage();
			glyph_pos.d_y =
				base_y - (img->getOffsetY() - img->getOffsetY() * y_scale);
			img->draw(buffer, glyph_pos,
				glyph->getSize(x_scale, y_scale), clip_rect, colours);
			glyph_pos.d_x += glyph->getAdvance(x_scale);
			// apply extra spacing to space chars
			if (text[c] == ' ')
				glyph_pos.d_x += space_extra;
		}
	}
}
